/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/
package org.apache.edgent.metrics;

import java.util.Hashtable;
import java.util.Map;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

import org.apache.edgent.oplet.OpletContext;

import com.codahale.metrics.ObjectNameFactory;

/**
 * A factory of metric {@code ObjectName} instances. 
 * <p>
 * The implementation relies on unique metric names generated by
 * {@link OpletContext#uniquify(String)} to
 * successfully parse the job and oplet id.
 */
public class MetricObjectNameFactory implements ObjectNameFactory {
    /** Prefix of all metric types. */
    public static final String TYPE_PREFIX = "metric";
    /** The {@code name} property key. */  
    public static final String KEY_NAME = "name";
    /** The {@code type} property key. */  
    public static final String KEY_TYPE = "type";
    /** The {@code jobId} property key. */  
    public static final String KEY_JOBID = "jobId";
    /** The {@code opId} (oplet id) property key. */  
    public static final String KEY_OPID = "opId";

    /*
     * This implementation avoids creating a dependency on the embedded 
     * runtime package by declaring local strings for the expected job and 
     * oplet id prefixes parsed from the metric name, which must be equal 
     * to those declared by the embedded runtime (see EtiaoJob.ID_PREFIX and 
     * Invocation.ID_PREFIX).
     */
    /** The prefix of the job id as serialized in the metric name. */    
    public static final String PREFIX_JOBID = "JOB_"; // Must be equal to EtiaoJob.PREFIX_ID
    /** The prefix of the oplet id as serialized in the metric name. */
    public static final String PREFIX_OPID = "OP_";  // Must be equal to Invocation.PREFIX_ID

    /**
     * Creates a JMX {@code ObjectName} from the given domain, metric type, 
     * and metric name.
     * <p>
     * If the metric name is an ObjectName pattern, or has a format which does
     * not correspond to a valid ObjectName, this implementation attempts to 
     * create an ObjectName using the quoted metric name instead. 
     * 
     * @param type the value of the "type" key property in the object name, 
     *      which represents the type of metric.
     * @param domain the domain part of the object name.
     * @param name the value of the "name" key property in the object name,
     *      which represents the metric name.
     * @throws RuntimeException wrapping a MalformedObjectNameException if 
     *      the implementation cannot create a valid object name.
     */
    @Override
    public ObjectName createName(String type, String domain, String name) {
        Hashtable<String,String> table = new Hashtable<String,String>();
        table.put(KEY_TYPE, TYPE_PREFIX + "." + type);
        table.put(KEY_NAME, name);
        addKeyProperties(name, table);
        try {
            ObjectName objectName = new ObjectName(domain, table);
            if (objectName.isPattern()) {
                table.put(KEY_NAME, ObjectName.quote(name));
                objectName = new ObjectName(domain, table);
            }
            return objectName;
        } catch (MalformedObjectNameException e) {
            try {
                table.put(KEY_NAME, ObjectName.quote(name));
                return new ObjectName(domain, table);
            } catch (MalformedObjectNameException e1) {
                // TODO slf4j logger.warn("Unable to register {} {} {}", type, domain, name, e1);
                throw new RuntimeException(e1);
            }
        }
    }

    /**
     * Extracts job and oplet identifier values from the specified buffer and 
     * adds the {@link #KEY_JOBID} and {@link #KEY_OPID} key properties to the 
     * specified properties map.  
     * <p>
     * Assumes that the job and oplet identifiers are concatenated (possibly with 
     * other strings as well) using '.' as a separator.
     * 
     * @param buf contains serialized job and oplet identifiers separated
     * @param properties key property map
     */
    protected void addKeyProperties(String buf, Map<String,String> properties) {
        addKeyProperty(buf, PREFIX_JOBID, KEY_JOBID, properties);
        addKeyProperty(buf, PREFIX_OPID, KEY_OPID, properties);
    }

    private void addKeyProperty(String buf, String prefix, String key, Map<String,String> properties) {
        String value = tokenStartingWith(buf, prefix);
        if (value != null) {
            properties.put(key, value);
        }
    }

    private static String tokenStartingWith(String buf, String prefix) {
        int start = buf.indexOf("." + prefix);
        String value = null;
        if (start != -1) {
            int end = buf.indexOf('.', start+1);
            if (end == -1) 
                end = buf.length();
            value = buf.substring(start+1, end);
        }
        return value;
    }
}
